/*++

    (c) 2010 by Microsoft Corp. All Rights Reserved.

    ksspi.h

    Description:
      Kernel Tempate Library (KTL): KSSPI

      Describes a general purpose SSPI wrapper for
      network security purposes.

    History:
      raymcc          16-Aug-2010         Initial version.
      leikong            Oct-2011.........Adding implementation
--*/

#pragma once

class KSspiAddress : public INetAddress
{
    K_FORCE_SHARED(KSspiAddress);

public:
    KSspiAddress(INetAddress::SPtr const & innerAddress, PCWSTR sspiTarget);

    virtual PSOCKADDR Get();

    virtual ULONG GetFormat();

    virtual PVOID GetUserData();

    virtual VOID SetUserData(__in PVOID Data);

    virtual size_t Size();

    virtual NTSTATUS ToStringW(__out_ecount(len) PWSTR buf, __inout ULONG & len) const;

    virtual NTSTATUS ToStringA(__out_ecount(len) PSTR buf, __inout ULONG & len) const;

    PWSTR SspiTarget();

private:
    INetAddress::SPtr const _innerAddress;
    KWString _sspiTarget;
};

//
// KSspiCredential
//
class KSspiCredential : public KShared<KSspiCredential>
{
public:
    typedef KSharedPtr<KSspiCredential> SPtr;

    KSspiCredential()
    {
        SecInvalidateHandle(&_value);
    }

#if KTL_USER_MODE == 0
    static SECURITY_STATUS AcquireSsl(
        _Out_ SPtr & credential,
        KAllocator & alloc,
        UCHAR const* certHashValue,
        WCHAR const* certStoreName,
        size_t cbCertStoreNameLength,
        ULONG credentialUse);

    static SECURITY_STATUS QueryRemoteCertificate(
        PCtxtHandle securityContext,
        __inout ULONG & cbCertificateChain,
        __out_bcount(cbCertificateChain) UCHAR * pbCertificateChain);

#endif

    static SECURITY_STATUS AcquireNegotiate(
        _Out_ SPtr & credential,
        _In_ KAllocator & alloc,
        _In_opt_ LPWSTR principal,
        unsigned long credentialUse,
        _In_opt_ void * logonId = nullptr,
        _In_opt_ void * authData = nullptr,
        _In_opt_ SEC_GET_KEY_FN getKeyFn = nullptr,
        _In_opt_ void * getKeyArgument = nullptr);

    static SECURITY_STATUS AcquireKerberos(
        _Out_ SPtr & credential,
        _In_ KAllocator & alloc,
        _In_opt_ LPWSTR principal,
        unsigned long credentialUse,
        _In_opt_ void * logonId = nullptr,
        _In_opt_ void * authData = nullptr,
        _In_opt_ SEC_GET_KEY_FN getKeyFn = nullptr,
        _In_opt_ void * getKeyArgument = nullptr);

    ~KSspiCredential()
    {
        FreeCredentialsHandle(&_value);
    }

    PCredHandle Handle()
    {
        return &_value;
    }

    PTimeStamp Expiry()
    {
        return &_expiry;
    }

private:
    CredHandle _value;
    TimeStamp _expiry;
};

//
// KSspiContext
//
class KSspiContext : public KObject<KSspiContext>
{
    K_DENY_COPY(KSspiContext);

public:
    typedef KUniquePtr<KSspiContext> UPtr;

    virtual ~KSspiContext() = 0;

    NTSTATUS StartContextCall(
        ULONG newlyReceived,
        _In_opt_ KAsyncContextBase::CompletionCallback callback,
        _In_opt_ KAsyncContextBase* const parentContext);

    PCtxtHandle Handle();

    bool InBound() const;

    NTSTATUS SecurityStatus() const;

    SECURITY_STATUS QueryContextAttributes(ULONG attribute, PVOID buffer);

    SecPkgContext_Sizes const & Sizes() const;

    KMemRef SendMemRef();

    KMemRef ReceiveMemRef();

    TimeStamp const & Expiry() const;

    void FreeBuffers();

    // SSL has their own message framing protocol. We need to save the extra data we may have read during
    // security negotiation and pass it onto the socket instance generated by SSPI socket accept/connect
    virtual KMemRef ExtraData() const;

protected:
    KSspiContext(
        _In_  KAllocator& alloc,
        _In_ PCredHandle credHandle,
        _In_opt_ PWSTR targetName,
        bool inbound,
        ULONG requirement,
        ULONG bufferSize);

private:
    SECURITY_STATUS InitializeContext(ULONG newlyReceived);
    SECURITY_STATUS AcceptContext(ULONG newlyReceived);

    virtual ULONG TargetDataRep() = 0;

    virtual PSecBufferDesc InputSecBufferDesc() = 0;

    virtual void SetInputSecBufferDesc(PVOID inputAddress, ULONG inputLength) = 0;

    virtual ULONG RemainingInputLength();

    virtual void SaveExtraInputIfAny() {}

    PCredHandle InputContext();

    PSecBufferDesc OutputSecBufferDesc();

    void OnNewInput(ULONG newlyReceived);

    void DeleteProcessedInput();

    void SetSendMemRef();

    void SetReceiveMemRef();

    ULONG OutputSize();

private:
    KAllocator & _allocator;
    PCredHandle _credHandle;
    KWString _targetName;
    ULONG _requirement;
    bool _inbound;
    bool _firstPass;

    CtxtHandle _handle;
    ULONG _contextAttr;
    TimeStamp _expiry;
    SECURITY_STATUS _securityStatus;

    SecBuffer _outputSecBuffer;
    SecBufferDesc _outputSecBufferDesc;
    KMemRef _outputBuffer;
    KMemRef _sendMemRef;

    KMemRef _inputBuffer;
    ULONG _inputLength;
    KMemRef _receiveMemRef;

    // Post blocking SSPI calls to system threadpool to avoid blocking KTL threadpool threads
    class ContextCallOp : public KAsyncContextBase, public KThreadPool::WorkItem
    {
    public:
        ContextCallOp(KSspiContext & sspiContext);

        NTSTATUS StartCall(
            ULONG newlyReceived = 0,
            _In_opt_ KAsyncContextBase::CompletionCallback Callback = 0,
            _In_opt_ KAsyncContextBase* const ParentContext = nullptr);

        void Execute();

    protected:
        void OnStart();

    private:
        // _sspiContext is part of KSspiSocket, which is kept alive by sspi KSspiSocketReadOp,
        // or KSspiSocketWriteOp, which parents KSspiNegotiateOp, which parents this ContextCallOp.
        KSspiContext & _sspiContext;
        ULONG _newlyReceived;
    };

    KSharedPtr<ContextCallOp> _contextCallOp;
};

class KSspiAuthorizationOp;

//
// KSspiTransport
//
class KSspiTransport : public ITransport
{
public:
    typedef KSharedPtr<KSspiTransport> SPtr;

    class AuthorizationContext : public KShared<AuthorizationContext>
    {
        K_FORCE_SHARED_WITH_INHERITANCE(AuthorizationContext);

    public:
        virtual NTSTATUS Authorize(_In_ PCtxtHandle securityContext, KSspiAuthorizationOp& operation) = 0;
    };

    virtual ~KSspiTransport() = 0 {}

    NTSTATUS CreateConnectOp(_Out_ ISocketConnectOp::SPtr& connectOp);

    NTSTATUS CreateListener(
        _In_  INetAddress::SPtr& Address,
        _Out_ ISocketListener::SPtr& Listener);

    NTSTATUS CreateBindAddressOp(_Out_ IBindAddressOp::SPtr& bindOp);

    virtual NTSTATUS CreateNetAddress(
        SOCKADDR_STORAGE const * address,
        _Out_ INetAddress::SPtr & netAddress);

    void Shutdown();

    NTSTATUS SpawnDefaultClientAddress(_Out_ INetAddress::SPtr& address);
    NTSTATUS SpawnDefaultListenerAddresses(_Inout_ KArray<INetAddress::SPtr>& addresses);

    ITransport::SPtr const & InnerTransport() const;

    AuthorizationContext* ClientAuthContext();
    AuthorizationContext* ServerAuthContext();

    #if KTL_USER_MODE
    LPWSTR SspiPackageName() { return _sspiPackageName.Buffer; }
    #else
    PSECURITY_STRING SspiPackageName() { return &_sspiPackageName; }
    #endif

    void SetClientContextRequirement(ULONG contextRequirement);
    void SetServerContextRequirement(ULONG contextRequirement);

    ULONG SspiMaxTokenSize();

    virtual NTSTATUS CreateSspiClientContext(
        _In_opt_ PWSTR targetName,
        _Out_ KSspiContext::UPtr & sspiContext) = 0;

    virtual NTSTATUS CreateSspiServerContext(_Out_ KSspiContext::UPtr & sspiContext) = 0;

    virtual NTSTATUS CreateSspiSocket(
        ISocket::SPtr && innerSocket,
        KSspiContext::UPtr && sspiContext,
        __out ISocket::SPtr & sspiSocket) = 0;

    // SSL has its own framing protocol, it doesn't need extra framing, so its implementation will simply
    // return the input innerSocket. Kerberos/NTLM doesn't have framing and thus requires this to maintain
    // message boundaries, for both negotiation messages and messages sent/received afterwards.
    virtual NTSTATUS CreateFramingSocket(
         ISocket::SPtr && innerSocket,
        __out ISocket::SPtr & framingSocket) = 0;

    void SetCredentials(
        KSspiCredential::SPtr && clientCredential,
        KSspiCredential::SPtr && serverCredential);

    ULONG NegotiationTimeout() const;
    void SetNegotiationTimeout(ULONG value);

    LONG MaxPendingInboundNegotiation() const;

protected:
    KSspiTransport(
        ITransport::SPtr const & innerTransport,
        KSspiCredential::SPtr const & clientCredential,
        KSspiCredential::SPtr const & serverCredential,
        AuthorizationContext::SPtr const & clientAuthContext,
        AuthorizationContext::SPtr const & serverAuthContext);

    KSspiCredential::SPtr ClientCredential() const;
    KSspiCredential::SPtr ServerCredential() const;

    ULONG ClientContextRequirement() const;
    ULONG ServerContextRequirement() const;

protected:
    SECURITY_STRING _sspiPackageName;

private:
    ITransport::SPtr const _innerTransport;

    mutable KSpinLock _thisLock;
    KSspiCredential::SPtr _clientCredential;
    KSspiCredential::SPtr _serverCredential;

    AuthorizationContext::SPtr _clientAuthContext;
    AuthorizationContext::SPtr _serverAuthContext;

    ULONG _clientContextRequirement;
    ULONG _serverContextRequirement;
    ULONG _sspiMaxTokenSize; // This may be larger than max message size of the inner transport

    ULONG _negotiationTimeoutInMs;
    LONG _maxPendingInboundNegotiation; // Per-listener limit for pending incoming security negotiations
};

class KSspiSocketListener;

//
// KSspiSocket: socket base class for all SSPI protocols
//
class KSspiSocket : public ISocket
{
    friend class KSspiNegotiateOp;
    friend class KSspiSocketReadOp;
    friend class KSspiSocketWriteOp;
    friend class KSslSocketReadOp;
    friend class KSslSocketWriteOp;
    friend class KKerbSocketReadOp;
    friend class KKerbSocketWriteOp;
    friend class KSspiSocketAcceptOp;

public:
    typedef KSharedPtr<KSspiSocket> SPtr;

    virtual ~KSspiSocket();

    void Close();

    NTSTATUS GetLocalAddress(__out INetAddress::SPtr& addr);

    NTSTATUS GetRemoteAddress(__out INetAddress::SPtr& addr);

    ULONG PendingOps();

    void SetUserData(__in PVOID Ptr);

    PVOID GetUserData();

    BOOLEAN Ok();

    PVOID GetSocketId(void);

    BOOLEAN Lock();

    void Unlock();

    ULONG GetMaxMessageSize();

protected:
    KSspiSocket(
        KSspiTransport::SPtr && sspiTransport,
        ISocket::SPtr && innerSocket,
        KSspiContext::UPtr && sspiContext);

    NTSTATUS InitializeBuffer(_Out_ KMemRef& memRef);

private:
    virtual NTSTATUS OnNegotiationSucceeded();

    NTSTATUS AcquireInboundNegotiationSlot(KSharedPtr<KSspiSocketListener> const & listener);
    void ReleaseInboundNegotiationSlotIfNeeded();

    KSspiContext & SspiContext();

    ISocket & InnerSocket();

    KSspiTransport & SspiTransport();

    NTSTATUS SecurityStatus() const;

    virtual NTSTATUS SetupBuffers() = 0;

    bool ShouldNegotiate() const;

    NTSTATUS StartNegotiate(
        __in_opt  KAsyncContextBase::CompletionCallback callback,
        __in_opt  KAsyncContextBase* const parentContext);

    void SaveNegotiateOp(KSspiNegotiateOp const * negotiateOp); // Save for debugging

    bool NegotiationSucceeded() const;

    NTSTATUS CompleteNegotiate(NTSTATUS status);

    KMemRef SavedCleartextMemRef();

    void SaveExtraCleartext(PVOID src, ULONG length);

    void ConsumeSavedCleartext(ULONG length);

protected:
    KSspiTransport::SPtr const _sspiTransport;
    KSharedPtr<KSspiSocketListener> _sspiListener;
    ISocket::SPtr const _innerSocket;
    void* _traceId;

    KSspiContext::UPtr _sspiContext;
    PVOID _userData;

    KAsyncLock _negotiateLock;
    bool _shouldNegotiate;
    bool _inboundNegotiationSlotHeld;
    KSspiNegotiateOp const * _sspiNegotiateOp;
    NTSTATUS _securityStatus;

    KAsyncLock _readLock;
    KAsyncLock _writeLock;

    ULONG _maxMessageSize;
    ULONG _messageBufferSize; // header + message + trailer

    KMemRef _readMemRef;
    KMemRef _writeMemRef;

    // When caller read buffer is not large enough, we need to save extra clear text and pass it to next read op
    PUCHAR _savedCleartextBuffer;
    KMemRef _savedCleartextMemRef;
};

//
// KSspiSocketListener
//
class KSspiSocketListener : public ISocketListener
{
    friend class KSspiTransport;
    friend class KSspiSocket;

public:
    typedef KSharedPtr<KSspiSocketListener> SPtr;

    NTSTATUS CreateListenOp(__out ISocketListenOp::SPtr& sspiListenOp);

    NTSTATUS CreateAcceptOp(__out ISocketAcceptOp::SPtr& sspiAcceptOp);

    void Shutdown();

    PVOID GetUserData()
    {
        return _userData;
    }

    void SetUserData(__in PVOID userData)
    {
        _userData = userData;
    }

    BOOLEAN AcceptTerminated()
    {
        return _innerSocketListener->AcceptTerminated();
    }

    void SetAcceptTerminated()
    {
        _innerSocketListener->SetAcceptTerminated();
    }

    KSspiTransport::SPtr const & SspiTransport() const
    {
        return _sspiTransport;
    }

    ISocketListener::SPtr const & InnerSocketListener() const
    {
        return _innerSocketListener;
    }

    void SetMaxPendingInboundNegotiation(LONG max);

private:
    KSspiSocketListener(
        KSspiTransport::SPtr && sspiTransport,
        ISocketListener::SPtr && socketListener);

    NTSTATUS AcquireNegotiationSlot();
    void ReleaseNegotiationSlot();
    bool IsNegotiationThrottleEnabled() const;

    KSspiTransport::SPtr const _sspiTransport;
    ISocketListener::SPtr const _innerSocketListener;
    PVOID _userData;

    KSpinLock _lock;
    LONG _maxPendingInboundNegotiation;
    LONG _pendingInboundNegotiation;
};

//
// KSspiAuthorizationOp
//
class KSspiAuthorizationOp : public KAsyncContextBase
{
    K_DENY_COPY(KSspiAuthorizationOp);

public:
    typedef KSharedPtr<KSspiAuthorizationOp> SPtr;

    static NTSTATUS StartAuthorize(
        _In_ KAllocator & alloc,
        _In_ KSspiTransport::AuthorizationContext * authorizationContext,
        _In_ PCtxtHandle sspiContext,
        KAsyncContextBase::CompletionCallback callback,
        KAsyncContextBase* const parentContext);

    void CompleteAuthorize(NTSTATUS status);

protected:
    void OnStart();

    void OnCompleted();

private:
    KSspiAuthorizationOp(
        _In_ KSspiTransport::AuthorizationContext * authorizationContext,
        _In_ PCtxtHandle sspiContext);

    NTSTATUS StartAuthorize(
        KAsyncContextBase::CompletionCallback callback,
        KAsyncContextBase* const parentContext);

    KSspiTransport::AuthorizationContext * _authorizationContext;
    PCtxtHandle const _sspiContext;
};

//
// KSspiSocketConnectOp
//
class KSspiSocketConnectOp : public ISocketConnectOp
{
    K_DENY_COPY(KSspiSocketConnectOp);
    friend class KSspiTransport;

public:
    typedef KSharedPtr<KSspiSocketConnectOp> SPtr;

    NTSTATUS StartConnect(
        __in      INetAddress::SPtr& connectFrom,
        __in      INetAddress::SPtr& connectTo,
        __in_opt  KAsyncContextBase::CompletionCallback callback = 0,
        __in_opt  KAsyncContextBase* const parentContext = nullptr);

    ISocket::SPtr GetSocket()
    {
        return _sspiSocket;
    }

    void SetUserData(__in PVOID Ptr)
    {
        _userData = Ptr;
    }

    PVOID GetUserData()
    {
        return _userData;
    }

protected:
    void OnStart();

    void OnReuse();

private:
    KSspiSocketConnectOp(KSspiTransport::SPtr && sspiTransport);

    void InnerConnectCallback(
        __in_opt KAsyncContextBase* const parent,
        __in KAsyncContextBase& completingOperation);

    void* _traceId;
    KSspiTransport::SPtr const _sspiTransport;
    INetAddress::SPtr _connectFrom;
    INetAddress::SPtr _connectTo;
    PVOID _userData;
    ISocketConnectOp::SPtr _innerConnectOp;
    ISocket::SPtr _sspiSocket;
};

//
// KSspiSocketAcceptOp
//
class KSspiSocketAcceptOp : public ISocketAcceptOp
{
    K_DENY_COPY(KSspiSocketAcceptOp);
    friend class KSspiSocketListener;

public:
    NTSTATUS StartAccept(
        __in_opt  KAsyncContextBase::CompletionCallback Callback = 0,
        __in_opt  KAsyncContextBase* const ParentContext = nullptr);

    void SetUserData(_In_ PVOID ptr);
    PVOID GetUserData();

    ISocket::SPtr GetSocket();

protected:
    void OnStart();
    void OnCancel();
    void OnReuse();

private:
    KSspiSocketAcceptOp(KSspiSocketListener::SPtr && sspiSocketListener);

    void InnerAcceptCallback(
        _In_opt_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

    void DelayTimerCallback(
        _In_opt_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

private:
    void* _traceId;
    KSspiSocketListener::SPtr const _sspiSocketListener;
    PVOID _userData;
    ISocketAcceptOp::SPtr _innerAcceptOp;
    ISocket::SPtr _sspiSocket;
    KTimer::SPtr _delayTimer;
};

//
// SSPI security context negotiation operation
//
class KSspiNegotiateOp : public KAsyncContextBase
{
    K_FORCE_SHARED(KSspiNegotiateOp);
    friend class KSspiSocket;

protected:
    void OnStart();
    void OnReuse();
    void OnCompleted();

private:
    KSspiNegotiateOp(KSspiSocket::SPtr && sspiSocket);

    void OnLockAcquired(BOOLEAN isAcquired, _In_ KAsyncLock &);

    void CompleteNegotiate(NTSTATUS status);

    void OnContextCallCompleted(
        _In_opt_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

    void StartSend();
    void SendCallback(
        _In_opt_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

    void StartReceive();
    void ReceiveCallback(
        _In_opt_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

    void CheckContextStatus(SECURITY_STATUS status);

    void AuthorizationCompleted(
        _In_opt_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

    void TimerCallback(
        _In_opt_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

    void TraceNegoInfo(NTSTATUS status);

private:
    void* _traceId;
    KSspiSocket::SPtr const _sspiSocket;
    bool _shouldUnlock;
    bool _startedNegotiate;
    KMemRef _sendMemRef;
    KMemRef _receiveMemRef;
    ISocketWriteOp::SPtr _sendOp;
    ISocketReadOp::SPtr _receiveOp;
    KTimer::SPtr _timer;
};

class KSspiSocketReadOp : public ISocketReadOp
{
    friend class KSspiSocket;

public:
    typedef KShared<KSspiSocketReadOp> SPtr;

    NTSTATUS StartRead(
        ULONG bufferCount,
        _In_       KMemRef* buffers,
        ULONG flags,
        _In_opt_ KAsyncContextBase::CompletionCallback callback = 0,
        _In_opt_   KAsyncContextBase* const parentContext = nullptr);

    ULONG BytesTransferred();

    ULONG BytesToTransfer();

    ISocket::SPtr GetSocket();

    void SetUserData(_In_ PVOID Ptr);

    PVOID GetUserData();

#if KTL_IO_TIMESTAMP
    KIoTimestamps const * IoTimestamps() override;
    KSspiTimestamps const * SspiTimestamps() override;
#endif

protected:
    void OnStart() override;

    KSspiSocketReadOp(KSspiSocket::SPtr && sspiSocket);

    void OnNegotiationCompleted(
        _In_opt_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

    void OnLockAcquired(BOOLEAN isAcquired, _In_ KAsyncLock &);

    void StartRead();

    void StartInnerReadOp();

    void InnerReadCallback(
        _In_opt_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

    virtual void DecryptMessage() = 0;

    void UnlockAndComplete(NTSTATUS status);

    bool OutputBuffersFull();

    bool ShouldComplete();

    ULONG CopyCleartext(PVOID from, ULONG length);

protected:
    void* _traceId;

    KSspiSocket::SPtr _sspiSocket;
    LockAcquiredCallback _lockAcquiredCallback;
    bool _shouldUnlock;

    ISocketReadOp::SPtr _innerSocketReadOp;
    CompletionCallback _innerReadCallback;

    ULONG _flags;
    PVOID _userData;

    KArray<KMemRef> _outputBuffers;
    ULONG _outputBufferSizeTotal;
    ULONG _currentOutputBuffer;
    ULONG _cleartextRead;

#if KTL_IO_TIMESTAMP
    // _sspiTimestamps.Reset() is never called for KSspiSocketReadOp, because a single decryption can produce multiple messages.
    // If _sspiTimestamps.Reset() is called, only the first message within a batch gets non-zero sspi timestamps.
    KSspiTimestamps _sspiTimestamps;
#endif

private:
    void DoDecryption();

};

//
// KSspiSocketWriteOp
//
class KSspiSocketWriteOp : public ISocketWriteOp
{
    friend class KSspiSocket;

public:
    NTSTATUS StartWrite(
        ULONG bufferCount,
        _In_ KMemRef* buffers,
        ULONG priority,
        _In_opt_ KAsyncContextBase::CompletionCallback callback = 0,
        _In_opt_ KAsyncContextBase* const parentContext = nullptr);

    ULONG BytesToTransfer();
    ULONG BytesTransferred();

    ISocket::SPtr GetSocket();

    void SetUserData(_In_ PVOID Ptr);
    PVOID GetUserData();

#if KTL_IO_TIMESTAMP
    KIoTimestamps const * IoTimestamps() override;
    KSspiTimestamps const * SspiTimestamps() override;
#endif

protected:
    void OnStart();

    void OnReuse();

    KSspiSocketWriteOp(KSspiSocket::SPtr && sspiSocket);

    void OnNegotiationCompleted(
        _In_opt_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

    void OnLockAcquired(BOOLEAN isAcquired, _In_ KAsyncLock &);

    virtual NTSTATUS EncryptMessage() = 0;

    void StartInnerWrite();

    void InnerWriteCallback(
        _In_ KAsyncContextBase* const parent,
        _In_ KAsyncContextBase& completingOperation);

    void UnlockAndComplete(NTSTATUS status);

protected:
    void* _traceId;

    KSspiSocket::SPtr _sspiSocket;
    LockAcquiredCallback _lockAcquiredCallback;
    bool _shouldUnlock;

    KArray<KMemRef> _inputBuffers;
    ULONG _priority;

    ISocketWriteOp::SPtr _innerSocketWriteOp;
    CompletionCallback _innerWriteCallback;

    PVOID _userData;
    ULONG _cleartextToTransfer;

#if KTL_IO_TIMESTAMP
    KSspiTimestamps _sspiTimestamps;
#endif
};
